from unittest.mock import patch

from django.core.exceptions import ValidationError
from django.test import TestCase, SimpleTestCase

from django_extensions.validators import (
    NoControlCharactersValidator,
    NoWhitespaceValidator,
    HexValidator,
)


class NoControlCharactersValidatorTests(TestCase):
    """Tests for NoControlCharactersValidator."""

    def test_should_raise_default_message_and_code_if_value_contains_new_line(self):
        self.validator = NoControlCharactersValidator()
        value_with_new_line = "test\nvalue"

        with self.assertRaises(ValidationError) as cm:
            self.validator(value_with_new_line)

        self.assertEqual(
            cm.exception.message,
            "Control Characters like new lines or tabs are not allowed.",
        )
        self.assertEqual(cm.exception.code, "no_control_characters")
        self.assertDictEqual(
            cm.exception.params, {"value": value_with_new_line, "whitelist": None}
        )

    def test_should_raise_custom_message_and_code_if_value_contains_tabs(self):
        self.validator = NoControlCharactersValidator(
            message="custom message", code="custom code"
        )
        value_with_tabs = "test\tvalue"

        with self.assertRaises(ValidationError) as cm:
            self.validator(value_with_tabs)

        self.assertEqual(cm.exception.message, "custom message")
        self.assertEqual(cm.exception.code, "custom code")
        self.assertDictEqual(
            cm.exception.params, {"value": value_with_tabs, "whitelist": None}
        )

    def test_should_not_raise_if_value_contains_characters_which_is_on_whitelist(self):
        self.validator = NoControlCharactersValidator(
            message="custom message", code="custom code", whitelist=["\n"]
        )
        value_with_new_line = "test\nvalue"

        result = self.validator(value_with_new_line)

        self.assertIsNone(result)


class NoWhiteSpaceValidatorTests(TestCase):
    """Tests for NoWhitespaceValidator."""

    def test_should_raise_default_message_and_code_if_value_has_leading_whitespace(
        self,
    ):
        self.validator = NoWhitespaceValidator()

        value_with_leading_whitespace = " test_value"

        with self.assertRaises(ValidationError) as cm:
            self.validator(value_with_leading_whitespace)

        self.assertEqual(
            cm.exception.message, "Leading and Trailing whitespaces are not allowed."
        )
        self.assertEqual(cm.exception.code, "no_whitespace")
        self.assertDictEqual(
            cm.exception.params, {"value": value_with_leading_whitespace}
        )

    def test_should_raise_custom_message_and_code_if_value_has_trailing_whitespace(
        self,
    ):
        self.validator = NoWhitespaceValidator(
            message="custom message", code="custom code"
        )
        value_with_trailing_whitespace = "test value "

        with self.assertRaises(ValidationError) as cm:
            self.validator(value_with_trailing_whitespace)

        self.assertEqual(cm.exception.message, "custom message")
        self.assertEqual(cm.exception.code, "custom code")
        self.assertDictEqual(
            cm.exception.params, {"value": value_with_trailing_whitespace}
        )

    def test_should_not_raise_if_value_doesnt_have_leading_or_trailing_whitespaces(
        self,
    ):
        self.validator = NoWhitespaceValidator()
        value_without_leading_or_trailing_whitespaces = "test value"

        result = self.validator(value_without_leading_or_trailing_whitespaces)

        self.assertIsNone(result)


class TestHexValidator(SimpleTestCase):
    def test_custom_message_and_code(self):
        self.validator = HexValidator(message="message", code="code")

        self.assertEqual(self.validator.message, "message")
        self.assertEqual(self.validator.code, "code")

    def test_equality_of_objs_with_obj_of_different_type(self):
        self.assertNotEqual(TypeError(), HexValidator())

    def test_equality_of_objs_with_different_code(self):
        self.assertNotEqual(HexValidator(code="1"), HexValidator(code="a"))

    def test_equality_of_objs_with_different_message(self):
        self.assertNotEqual(
            HexValidator(code="code", message="a"),
            HexValidator(code="code", message="acb"),
        )

    def test_equality_of_objs_with_same_code_and_message(self):
        self.assertEqual(
            HexValidator(code="c", message="m"), HexValidator(code="c", message="m")
        )

    def test_fixed_length(self):
        value = "abcd"
        self.validator = HexValidator(length=5)

        with self.assertRaises(ValidationError) as err:
            self.validator(value)

        self.assertEqual(
            str(err.exception), "['Invalid length. Must be 5 characters.']"
        )
        self.assertEqual(err.exception.code, "hex_only_length")

    def test_min_length(self):
        value = "a"
        self.validator = HexValidator(min_length=5)

        with self.assertRaises(ValidationError) as err:
            self.validator(value)

        self.assertEqual(
            str(err.exception), "['Ensure that there are more than 5 characters.']"
        )
        self.assertEqual(err.exception.code, "hex_only_min_length")

    def test_with_max_length(self):
        value = "abcd"
        self.validator = HexValidator(max_length=2)

        with self.assertRaises(ValidationError) as err:
            self.validator(value)

        self.assertEqual(
            str(err.exception), "['Ensure that there are no more than 2 characters.']"
        )
        self.assertEqual(err.exception.code, "hex_only_max_length")

    def test_invalid_type(self):
        value = 1
        with patch("django_extensions.validators.force_str", return_value=1):
            self.validator = HexValidator()

            with self.assertRaises(ValidationError) as err:
                self.validator(value)

        self.assertEqual(str(err.exception), "['Only a hex string is allowed.']")
        self.assertEqual(err.exception.code, "hex_only")

    def test_invalid_hex(self):
        value = "1"
        self.validator = HexValidator()

        with self.assertRaises(ValidationError) as err:
            self.validator(value)

        self.assertEqual(str(err.exception), "['Only a hex string is allowed.']")
        self.assertEqual(err.exception.code, "hex_only")

    def test_valid_hex(self):
        value = "b901ef"
        self.validator = HexValidator()

        result = self.validator(value)

        self.assertIsNone(result)
